﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;

namespace GoodStuff.Data
{
    public abstract class BaseValidator<T>:IRuleFactory where T : class
    {
        private List<IRule> _rules = new List<IRule>();

        public BaseValidator()
        {
            Setup();
        }

        protected abstract void Setup();

        IEnumerable<IRule> IRuleFactory.GetRules()
        {
            return _rules;
        }

        protected RuleBuilder<TParam> Verify<TParam>(Expression<Func<T,TParam>> property)
        {
            var body = property.Body as MemberExpression;
            if (body == null)
            {
                throw new ArgumentException("Cannot verify '" + property.Body.ToString() + "'");
            }
            var memberName = body.Member.Name;           
            var typeName = body.Member.DeclaringType;

            return new RuleBuilder<TParam>(_rules, typeName, memberName);
        }
    }

    public class RuleBuilder<TParam>
    {
        private Type type;
        private string memberName;
        private IList<IRule> _rules;

        public RuleBuilder(IList<IRule> rules, Type type, string memberName)
        {
            this._rules = rules;
            this.type = type;
            this.memberName = memberName;
        }

        public RuleBuilder<TParam> IsRequired()
        {
            _rules.Add(new IsRequiredRule() { Property = type.GetProperty(memberName) });
            return this;
        }

        public RuleBuilder<TParam> IsMaximum(int maxLength)
        {
            _rules.Add(new MaximumLengthRule() { Property = type.GetProperty(memberName), MaxLength = maxLength });
            return this;
        }

        public RuleBuilder<TParam> Matches(string expression)
        {
            _rules.Add(new MatchExpressionRule() { Property = type.GetProperty(memberName), RegularExpression = new Regex(expression, RegexOptions.Compiled)});
            return this;
        }

        public RuleBuilder<TParam> Validates(Func<TParam, bool> guard)
        {
            _rules.Add(new DelegatedRule<TParam>() { Property = type.GetProperty(memberName), Guard = guard } );
            return this;
        }
    }

    //public abstract class CompoundValidator : IRule, IRuleFactory
    //{
    //    private List<IRule> _rules = new List<IRule>();

    //    public bool Validate(object target)
    //    {
    //        throw new NotImplementedException();
    //    }

    //    public string Message
    //    {
    //        get { throw new NotImplementedException(); }
    //    }
    //}


    public abstract class PropertyValidator : IRule
    {
        private PropertyInfo _property;

        public PropertyInfo Property
        {
            get
            {
                return _property; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("Property cannot be null");
                }
                _property = value;
            }
        }

        public abstract string Message { get; }

        public bool Validate(object target)
        {
            return ValidateValue(Property.GetValue(target, null));
        }

        protected abstract bool ValidateValue(object value);
    }

    public class IsRequiredRule : PropertyValidator
    {
        public override string Message
        {
            get { return string.Format("{0} is a required field", this.Property.Name); }
        }

        protected override bool ValidateValue(object target)
        {
            if (target is string)
            {
                return !string.IsNullOrEmpty((string)target);
            }
            //todo: implement more types.
            return false;
        }
    }

    public class DelegatedRule<T> : PropertyValidator
    {
        public Func<T, Boolean> Guard { get; set; }

        public override string Message
        {
            get { return string.Format("{0} is not valid", this.Property.Name); }
        }

        protected override bool ValidateValue(object target)
        {
            return Guard((T)target);
        }
    }

    public class MatchExpressionRule : PropertyValidator
    {      
        public override string Message
        {
            get { return string.Format("{0} does not match the expression.", this.Property.Name); }
        }

        public Regex RegularExpression { get; set; }

        protected override bool ValidateValue(object target)
        {
            if (target is string && !string.IsNullOrEmpty((string)target))
            {
                return RegularExpression.IsMatch((string)target);
            }
            //todo: implement more types.
            return true;
        }
    }



    public class MaximumLengthRule : PropertyValidator
    {
        public override string Message
        {
            get { return string.Format("{0} is too long", this.Property.Name); }
        }

        public int MaxLength { get; set; }

        protected override bool ValidateValue(object target)
        {
            if (target is string)
            {
                return ((string)target).Length <= MaxLength;
            }
            //todo: implement more types.
            return false;
        }
    }

    public interface IRule
    {
        bool Validate(object target);
        string Message { get; }
    }

    public interface IRuleFactory
    {
        IEnumerable<IRule> GetRules();
    }
   
    public class ValidationResults
    {
        private List<IValidationError> _errors;

        public ValidationResults()
        {
            _errors = new List<IValidationError>();
        }

        public bool IsValid { get { return _errors.Count == 0; } }
        public List<IValidationError> Errors { get { return _errors; } }
    }

    public interface IValidationError
    {
        string PropertyName { get; }
        string Message { get; }
    }

    public class ValidationError : IValidationError
    {
        public ValidationError(string message)
        {
            // TODO: Complete member initialization
            Message = message;
        }     
        public string PropertyName { get; private set; }
        public string Message { get; private set; }
    }

    public static class Validation
    {
        public static ValidationResults Validate<T>(IRuleFactory rules, T target) where T:class
        {
            ValidationResults results = new ValidationResults();            

            foreach (IRule rule in rules.GetRules())
            {
                if(!rule.Validate(target))
                {
                    results.Errors.Add(new ValidationError(rule.Message));
                }
            }
            return results;
        }

        public static void Assert<T>(IRuleFactory rules, T target) where T : class
        {
            var results = Validate(rules, target);
            if (!results.IsValid)
            {
                throw new ValidationException(results);
            }
        }
    }

    public class ValidationException : Exception
    {
        public ValidationException(ValidationResults results)
        {
            Results = results;
        }

        public ValidationResults Results { get; private set; }

        public override string Message
        {
            get
            {
                return string.Join("\n", Results.Errors.Select(p => p.Message).ToArray());
            }
        }
    }
}
